OAIなS3+CloudFront環境でタグを使って特定のS3オブジェクトだけ非公開にする
はじめに
清水です。Amazon CloudFrontのオリジンアクセスアイデンティティ(Origin Access Identity: OAI)を使ってS3へのアクセスをCloudFrontからのみに限定することができます。CloudFrontディストリビューション作成時などに設定されるデフォルトのOAIを使用するS3バケットポリシーの場合、対象のS3バケット内のオブジェクトはすべてCloudFront経由で公開される状態となります。この状態から、S3バケット内の特定のオブジェクトのみを非公開(CloudFront経由でアクセスできない)にする方法を考えてみました。いろいろと方法はあるかと思いますが、前提としてS3のアクセスコントロールリスト(Access Control List: ACL)は使用しないこととしました。ACLを使うことでアクセス権限管理が複雑になってしまうので、なるべくシンプルに済ませたい、とういう考えです。
今回試してみた具体的な方法は、S3オブジェクトに特定のタグを付与し、このタグ情報を用いてOAIによるアクセス可否を判断するようS3バケットポリシーを編集する、というものです。目的自体は達成できたかと思いますが、タグを外したあとにまた付ける、といったような場合のCloudFrontのキャッシュの挙動には注意が必要です。以下、設定方法や実際の挙動などのまとめになります。
デフォルト状態のOAI用S3バケットポリシーを確認する
まずはCloudFrontのOAI使用時のデフォルトのS3バケットポリシーを確認してみます。なおここでの「デフォルト」は、マネジメントコンソールよりCloudFrontディストリビューション作成時に自動的に設定されるもの、とします。
{ "Version": "2008-10-17", "Id": "PolicyForCloudFrontPrivateContent", "Statement": [ { "Sid": "1", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E3VRXXXXXXXXXX" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-s3-bucket/*" } ] }
なおS3ユーザガイドやCloudFrontデベロッパーズガイドにも、同様のバケットポリシーの記載があります。
- バケットポリシーの例 - Amazon Simple Storage Service
- オリジンアクセスアイデンティティを使用して Amazon S3 コンテンツへのアクセスを制限する - Amazon CloudFront
ざっくり内容を確認すれば、CloudFront側はPrincipal
で指定されたIDでS3にアクセス、このPrincipalに対して対象バケットのオブジェクトにs3:GetObject
を許可することとなります。(結果、CloudFrontからはS3のオブジェクトにアクセスが可能となります。)
オブジェクトに付与されているタグでアクセスを制限する
上記のデフォルトのOAI用のS3バケットポリシーから、S3オブジェクトに付与されているタグの内容でアクセスを制限することを考えます。今回、特定のオブジェクトのみ非公開とするということで、このオブジェクトにはprivate:true
というタグを付けるとします。
S3バケットポリシー側では、このタグの内容(private:true
)以外であればアクセス(s3:GetObject)を許可する、としました。具体的には以下となります。(追加箇所をハイライト表示しています。)
{ "Version": "2008-10-17", "Id": "PolicyForCloudFrontPrivateContent", "Statement": [ { "Sid": "1", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E3VRXXXXXXXXXX" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-s3-bucket/*", "Condition": { "StringNotEquals": { "s3:ExistingObjectTag/private": "true" } } } ] }
タグの内容に基づくポリシーの記載方法は以下などを参考にしました。
実際に挙動を確認してみます。private-object.html
というオブジェクトをアップロード、この際にprivate:true
のタグを付与しました。
CloudFront経由でアクセスしてみます。以下のように403が返りました。オブジェクトが非公開になっていますね。
$ curl -i https://www.example.com/private-object.html HTTP/2 403 content-type: application/xml date: Tue, 31 Aug 2021 07:57:09 GMT server: AmazonS3 x-cache: Error from cloudfront via: 1.1 409082e9caee4a1cdc1a950363f5172d.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: yDYb_RudTwuWxBKTG95lEbS2VdAiIRfDpMLeR17uXnW4rWfHplNC7g== <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>1R9C55Y03CNAHGEP</RequestId><HostId>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</HostId></Error>
公開するためにはオブジェクトのタグをprivate:true
以外(もしくはprivateタグを削除)にします。
$ aws s3api put-object-tagging --bucket my-s3-bucket --key private-object.html --tagging '{"TagSet": [{ "Key": "private", "Value": "false" }]}' { "VersionId": "y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn" } $ aws s3api get-object-tagging --bucket my-s3-bucket --key private-object.html{ "VersionId": "y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn", "TagSet": [ { "Key": "private", "Value": "false" } ] }
今度はCloudFront経由でアクセスできました。
$ curl -i https://www.example.com/private-object.html HTTP/2 200 content-type: text/html content-length: 84 date: Tue, 31 Aug 2021 08:03:10 GMT last-modified: Tue, 31 Aug 2021 07:56:34 GMT etag: "e6f4e2329a2c6062bacd8202a383aa5d" x-amz-version-id: y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn accept-ranges: bytes server: AmazonS3 x-cache: Miss from cloudfront via: 1.1 082329696d49819d97bc7da98006304c.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C1 x-amz-cf-id: r8zwlwXHBzWk94T3QtIpDp52G_VG43cdRf5YbXruh8hthOsGFaziwg== <html> <head> <title>private</title> </head> <body>private</body> </html>
再度、タグをprivate:true
にすることで非公開になりますが、CloudFrontのキャッシュ有効期限内であればオリジン側の公開・非公開にかかわらず、CloudFrontからアクセスが可能な点に注意しましょう。
$ aws s3api put-object-tagging --bucket my-s3-bucket --key private-object.html --tagging '{"TagSet": [{ "Key": "private", "Value": "true" }]}' { "VersionId": "y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn" } $ aws s3api get-object-tagging --bucket my-s3-bucket --key private-object.html{ "VersionId": "y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn", "TagSet": [ { "Key": "private", "Value": "true" } ] }
以下はCloudFrontエッジにキャッシュがあるため、CloudFront経由のアクセスが可能だったパターンです。
$ curl -i https://www.example.com/private-object.html HTTP/2 200 content-type: text/html content-length: 84 date: Tue, 31 Aug 2021 08:03:10 GMT last-modified: Tue, 31 Aug 2021 07:56:34 GMT etag: "e6f4e2329a2c6062bacd8202a383aa5d" x-amz-version-id: y9I7wTfWyDokhZ9gbearRYMoM1CVt.Yn accept-ranges: bytes server: AmazonS3 x-cache: Hit from cloudfront via: 1.1 082329696d49819d97bc7da98006304c.cloudfront.net (CloudFront) x-amz-cf-pop: NRT57-C1 x-amz-cf-id: EIs8LqWY9dPFbtsf-wAO--_tiuvb9mXXv6i4AsVCn8vIFzooTLzUaw== age: 21 <html> <head> <title>private</title> </head> <body>private</body> </html>
以下は、キャッシュがない異なるエッジのため、CloudFront経由のアクセスが不可だったパターンです。
$ curl -i https://www.example.com/private-object.html HTTP/2 403 content-type: application/xml date: Tue, 31 Aug 2021 08:04:36 GMT server: AmazonS3 x-cache: Error from cloudfront via: 1.1 786110c43ee4ea47c9ade0944c256de0.cloudfront.net (CloudFront) x-amz-cf-pop: NRT51-C2 x-amz-cf-id: gK5-13m7hcFqD6HIq2WH8jhTSxAD0A8mHHjgQoVhn1wgcE6pB5L7aA== <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>CQYW5S95JRH72NFB</RequestId><HostId>yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy</HostId></Error>
まとめ
CloudFrontのOrigin Access Identityを使用してS3バケットポリシーによるアクセス制限をしている環境で、タグを付与した特定のS3オブジェクトのみをCloudFront経由で非公開にする、ということをやってみました。CloudFrontのキャッシュが残っている場合は、S3側でタグを編集して非公開の状態にしても即時に反映されない点は要注意で考慮すべき箇所かと思いますが、S3にファイルアップロードした段階では非公開にしておきたい場合などに利用できるかなと考えています。